1   /*
2    * Copyright (c) 2003, 2010, Oracle and/or its affiliates. All rights reserved.
3    * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4    *
5    * This code is free software; you can redistribute it and/or modify it
6    * under the terms of the GNU General Public License version 2 only, as
7    * published by the Free Software Foundation.  Oracle designates this
8    * particular file as subject to the "Classpath" exception as provided
9    * by Oracle in the LICENSE file that accompanied this code.
10   *
11   * This code is distributed in the hope that it will be useful, but WITHOUT
12   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13   * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14   * version 2 for more details (a copy is included in the LICENSE file that
15   * accompanied this code).
16   *
17   * You should have received a copy of the GNU General Public License version
18   * 2 along with this work; if not, write to the Free Software Foundation,
19   * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20   *
21   * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22   * or visit www.oracle.com if you need additional information or have any
23   * questions.
24   */
25  
26  package com.sun.java.util.jar.pack;
27  
28  import java.io.IOException;
29  import java.io.InputStream;
30  import java.io.PrintStream;
31  import java.util.Arrays;
32  
33  /**
34   * Histogram derived from an integer array of events (int[]).
35   * @author John Rose
36   */
37  final class Histogram {
38      // Compact histogram representation:  4 bytes per distinct value,
39      // plus 5 words per distinct count.
40      protected final int[][] matrix;  // multi-row matrix {{counti,valueij...}}
41      protected final int     totalWeight;  // sum of all counts
42  
43      // These are created eagerly also, since that saves work.
44      // They cost another 8 bytes per distinct value.
45      protected final int[]   values;  // unique values, sorted by value
46      protected final int[]   counts;  // counts, same order as values
47  
48      private static final long LOW32 = (long)-1 >>> 32;
49  
50      /** Build a histogram given a sequence of values.
51       *  To save work, the input should be sorted, but need not be.
52       */
53      public
54      Histogram(int[] valueSequence) {
55          long[] hist2col = computeHistogram2Col(maybeSort(valueSequence));
56          int[][] table = makeTable(hist2col);
57          values = table[0];
58          counts = table[1];
59          this.matrix = makeMatrix(hist2col);
60          this.totalWeight = valueSequence.length;
61          assert(assertWellFormed(valueSequence));
62      }
63      public
64      Histogram(int[] valueSequence, int start, int end) {
65          this(sortedSlice(valueSequence, start, end));
66      }
67  
68      /** Build a histogram given a compact matrix of counts and values. */
69      public
70      Histogram(int[][] matrix) {
71          // sort the rows
72          matrix = normalizeMatrix(matrix);  // clone and sort
73          this.matrix = matrix;
74          int length = 0;
75          int weight = 0;
76          for (int i = 0; i < matrix.length; i++) {
77              int rowLength = matrix[i].length-1;
78              length += rowLength;
79              weight += matrix[i][0] * rowLength;
80          }
81          this.totalWeight = weight;
82          long[] hist2col = new long[length];
83          int fillp = 0;
84          for (int i = 0; i < matrix.length; i++) {
85              for (int j = 1; j < matrix[i].length; j++) {
86                  // sort key is value, so put it in the high 32!
87                  hist2col[fillp++] = ((long) matrix[i][j] << 32)
88                                    | (LOW32 & matrix[i][0]);
89              }
90          }
91          assert(fillp == hist2col.length);
92          Arrays.sort(hist2col);
93          int[][] table = makeTable(hist2col);
94          values = table[1]; //backwards
95          counts = table[0]; //backwards
96          assert(assertWellFormed(null));
97      }
98  
99      /** Histogram of int values, reported compactly as a ragged matrix,
100      *  indexed by descending frequency rank.
101      *  <p>
102      *  Format of matrix:
103      *  Each row in the matrix begins with an occurrence count,
104      *  and continues with all int values that occur at that frequency.
105      *  <pre>
106      *  int[][] matrix = {
107      *    { count1, value11, value12, value13, ...  },
108      *    { count2, value21, value22, ... },
109      *    ...
110      *  }
111      *  </pre>
112      *  The first column of the matrix { count1, count2, ... }
113      *  is sorted in descending order, and contains no duplicates.
114      *  Each row of the matrix (apart from its first element)
115      *  is sorted in ascending order, and contains no duplicates.
116      *  That is, each sequence { valuei1, valuei2, ... } is sorted.
117      */
118     public
119     int[][] getMatrix() { return matrix; }
120 
121     public
122     int getRowCount() { return matrix.length; }
123 
124     public
125     int getRowFrequency(int rn) { return matrix[rn][0]; }
126 
127     public
128     int getRowLength(int rn) { return matrix[rn].length-1; }
129 
130     public
131     int getRowValue(int rn, int vn) { return matrix[rn][vn+1]; }
132 
133     public
134     int getRowWeight(int rn) {
135         return getRowFrequency(rn) * getRowLength(rn);
136     }
137 
138     public
139     int getTotalWeight() {
140         return totalWeight;
141     }
142 
143     public
144     int getTotalLength() {
145         return values.length;
146     }
147 
148     /** Returns an array of all values, sorted. */
149     public
150     int[] getAllValues() {
151 
152         return values;
153     }
154 
155     /** Returns an array parallel with {@link #getValues},
156      *  with a frequency for each value.
157      */
158     public
159     int[] getAllFrequencies() {
160         return counts;
161     }
162 
163     private static double log2 = Math.log(2);
164 
165     public
166     int getFrequency(int value) {
167         int pos = Arrays.binarySearch(values, value);
168         if (pos < 0)  return 0;
169         assert(values[pos] == value);
170         return counts[pos];
171     }
172 
173     public
174     double getBitLength(int value) {
175         double prob = (double) getFrequency(value) / getTotalWeight();
176         return - Math.log(prob) / log2;
177     }
178 
179     public
180     double getRowBitLength(int rn) {
181         double prob = (double) getRowFrequency(rn) / getTotalWeight();
182         return - Math.log(prob) / log2;
183     }
184 
185     public
186     interface BitMetric {
187         public double getBitLength(int value);
188     }
189     private final BitMetric bitMetric = new BitMetric() {
190         public double getBitLength(int value) {
191             return Histogram.this.getBitLength(value);
192         }
193     };
194     public BitMetric getBitMetric() {
195         return bitMetric;
196     }
197 
198     /** bit-length is negative entropy:  -H(matrix). */
199     public
200     double getBitLength() {
201         double sum = 0;
202         for (int i = 0; i < matrix.length; i++) {
203             sum += getRowBitLength(i) * getRowWeight(i);
204         }
205         assert(0.1 > Math.abs(sum - getBitLength(bitMetric)));
206         return sum;
207     }
208 
209     /** bit-length in to another coding (cross-entropy) */
210     public
211     double getBitLength(BitMetric len) {
212         double sum = 0;
213         for (int i = 0; i < matrix.length; i++) {
214             for (int j = 1; j < matrix[i].length; j++) {
215                 sum += matrix[i][0] * len.getBitLength(matrix[i][j]);
216             }
217         }
218         return sum;
219     }
220 
221     static private
222     double round(double x, double scale) {
223         return Math.round(x * scale) / scale;
224     }
225 
226     /** Sort rows and columns.
227      *  Merge adjacent rows with the same key element [0].
228      *  Make a fresh copy of all of it.
229      */
230     public int[][] normalizeMatrix(int[][] matrix) {
231         long[] rowMap = new long[matrix.length];
232         for (int i = 0; i < matrix.length; i++) {
233             if (matrix[i].length <= 1)  continue;
234             int count = matrix[i][0];
235             if (count <= 0)  continue;
236             rowMap[i] = (long) count << 32 | i;
237         }
238         Arrays.sort(rowMap);
239         int[][] newMatrix = new int[matrix.length][];
240         int prevCount = -1;
241         int fillp1 = 0;
242         int fillp2 = 0;
243         for (int i = 0; ; i++) {
244             int[] row;
245             if (i < matrix.length) {
246                 long rowMapEntry = rowMap[rowMap.length-i-1];
247                 if (rowMapEntry == 0)  continue;
248                 row = matrix[(int)rowMapEntry];
249                 assert(rowMapEntry>>>32 == row[0]);
250             } else {
251                 row = new int[]{ -1 };  // close it off
252             }
253             if (row[0] != prevCount && fillp2 > fillp1) {
254                 // Close off previous run.
255                 int length = 0;
256                 for (int p = fillp1; p < fillp2; p++) {
257                     int[] row0 = newMatrix[p];  // previously visited row
258                     assert(row0[0] == prevCount);
259                     length += row0.length-1;
260                 }
261                 int[] row1 = new int[1+length];  // cloned & consolidated row
262                 row1[0] = prevCount;
263                 int rfillp = 1;
264                 for (int p = fillp1; p < fillp2; p++) {
265                     int[] row0 = newMatrix[p];  // previously visited row
266                     assert(row0[0] == prevCount);
267                     System.arraycopy(row0, 1, row1, rfillp, row0.length-1);
268                     rfillp += row0.length-1;
269                 }
270                 if (!isSorted(row1, 1, true)) {
271                     Arrays.sort(row1, 1, row1.length);
272                     int jfillp = 2;
273                     // Detect and squeeze out duplicates.
274                     for (int j = 2; j < row1.length; j++) {
275                         if (row1[j] != row1[j-1])
276                             row1[jfillp++] = row1[j];
277                     }
278                     if (jfillp < row1.length) {
279                         // Reallocate because of lost duplicates.
280                         int[] newRow1 = new int[jfillp];
281                         System.arraycopy(row1, 0, newRow1, 0, jfillp);
282                         row1 = newRow1;
283                     }
284                 }
285                 newMatrix[fillp1++] = row1;
286                 fillp2 = fillp1;
287             }
288             if (i == matrix.length)
289                 break;
290             prevCount = row[0];
291             newMatrix[fillp2++] = row;
292         }
293         assert(fillp1 == fillp2);  // no unfinished business
294         // Now drop missing rows.
295         matrix = newMatrix;
296         if (fillp1 < matrix.length) {
297             newMatrix = new int[fillp1][];
298             System.arraycopy(matrix, 0, newMatrix, 0, fillp1);
299             matrix = newMatrix;
300         }
301         return matrix;
302     }
303 
304     public
305     String[] getRowTitles(String name) {
306         int totalUnique = getTotalLength();
307         int ltotalWeight = getTotalWeight();
308         String[] histTitles = new String[matrix.length];
309         int cumWeight = 0;
310         int cumUnique = 0;
311         for (int i = 0; i < matrix.length; i++) {
312             int count  = getRowFrequency(i);
313             int unique = getRowLength(i);
314             int weight = getRowWeight(i);
315             cumWeight += weight;
316             cumUnique += unique;
317             long wpct = ((long)cumWeight * 100 + ltotalWeight/2) / ltotalWeight;
318             long upct = ((long)cumUnique * 100 + totalUnique/2) / totalUnique;
319             double len = getRowBitLength(i);
320             assert(0.1 > Math.abs(len - getBitLength(matrix[i][1])));
321             histTitles[i] = name+"["+i+"]"
322                 +" len="+round(len,10)
323                 +" ("+count+"*["+unique+"])"
324                 +" ("+cumWeight+":"+wpct+"%)"
325                 +" ["+cumUnique+":"+upct+"%]";
326         }
327         return histTitles;
328     }
329 
330     /** Print a report of this histogram.
331      */
332     public
333     void print(PrintStream out) {
334         print("hist", out);
335     }
336 
337     /** Print a report of this histogram.
338      */
339     public
340     void print(String name, PrintStream out) {
341         print(name, getRowTitles(name), out);
342     }
343 
344     /** Print a report of this histogram.
345      */
346     public
347     void print(String name, String[] histTitles, PrintStream out) {
348         int totalUnique = getTotalLength();
349         int ltotalWeight = getTotalWeight();
350         double tlen = getBitLength();
351         double avgLen = tlen / ltotalWeight;
352         double avg = (double) ltotalWeight / totalUnique;
353         String title = (name
354                         +" len="+round(tlen,10)
355                         +" avgLen="+round(avgLen,10)
356                         +" weight("+ltotalWeight+")"
357                         +" unique["+totalUnique+"]"
358                         +" avgWeight("+round(avg,100)+")");
359         if (histTitles == null) {
360             out.println(title);
361         } else {
362             out.println(title+" {");
363             StringBuffer buf = new StringBuffer();
364             for (int i = 0; i < matrix.length; i++) {
365                 buf.setLength(0);
366                 buf.append("  ").append(histTitles[i]).append(" {");
367                 for (int j = 1; j < matrix[i].length; j++) {
368                     buf.append(" ").append(matrix[i][j]);
369                 }
370                 buf.append(" }");
371                 out.println(buf);
372             }
373             out.println("}");
374         }
375     }
376 
377 /*
378     public static
379     int[][] makeHistogramMatrix(int[] values) {
380         // Make sure they are sorted.
381         values = maybeSort(values);
382         long[] hist2col = computeHistogram2Col(values);
383         int[][] matrix = makeMatrix(hist2col);
384         return matrix;
385     }
386 */
387 
388     private static
389     int[][] makeMatrix(long[] hist2col) {
390         // Sort by increasing count, then by increasing value.
391         Arrays.sort(hist2col);
392         int[] counts = new int[hist2col.length];
393         for (int i = 0; i < counts.length; i++) {
394             counts[i] = (int)( hist2col[i] >>> 32 );
395         }
396         long[] countHist = computeHistogram2Col(counts);
397         int[][] matrix = new int[countHist.length][];
398         int histp = 0;  // cursor into hist2col (increasing count, value)
399         int countp = 0; // cursor into countHist (increasing count)
400         // Do a join between hist2col (resorted) and countHist.
401         for (int i = matrix.length; --i >= 0; ) {
402             long countAndRep = countHist[countp++];
403             int count  = (int) (countAndRep);  // what is the value count?
404             int repeat = (int) (countAndRep >>> 32);  // # times repeated?
405             int[] row = new int[1+repeat];
406             row[0] = count;
407             for (int j = 0; j < repeat; j++) {
408                 long countAndValue = hist2col[histp++];
409                 assert(countAndValue >>> 32 == count);
410                 row[1+j] = (int) countAndValue;
411             }
412             matrix[i] = row;
413         }
414         assert(histp == hist2col.length);
415         return matrix;
416     }
417 
418     private static
419     int[][] makeTable(long[] hist2col) {
420         int[][] table = new int[2][hist2col.length];
421         // Break apart the entries in hist2col.
422         // table[0] gets values, table[1] gets entries.
423         for (int i = 0; i < hist2col.length; i++) {
424             table[0][i] = (int)( hist2col[i] );
425             table[1][i] = (int)( hist2col[i] >>> 32 );
426         }
427         return table;
428     }
429 
430     /** Simple two-column histogram.  Contains repeated counts.
431      *  Assumes input is sorted.  Does not sort output columns.
432      *  <p>
433      *  Format of result:
434      *  <pre>
435      *  long[] hist = {
436      *    (count1 << 32) | (value1),
437      *    (count2 << 32) | (value2),
438      *    ...
439      *  }
440      *  </pre>
441      *  In addition, the sequence {valuei...} is guaranteed to be sorted.
442      *  Note that resorting this using Arrays.sort() will reorder the
443      *  entries by increasing count.
444      */
445     private static
446     long[] computeHistogram2Col(int[] sortedValues) {
447         switch (sortedValues.length) {
448         case 0:
449             return new long[]{ };
450         case 1:
451             return new long[]{ ((long)1 << 32) | (LOW32 & sortedValues[0]) };
452         }
453         long[] hist = null;
454         for (boolean sizeOnly = true; ; sizeOnly = false) {
455             int prevIndex = -1;
456             int prevValue = sortedValues[0] ^ -1;  // force a difference
457             int prevCount = 0;
458             for (int i = 0; i <= sortedValues.length; i++) {
459                 int thisValue;
460                 if (i < sortedValues.length)
461                     thisValue = sortedValues[i];
462                 else
463                     thisValue = prevValue ^ -1;  // force a difference at end
464                 if (thisValue == prevValue) {
465                     prevCount += 1;
466                 } else {
467                     // Found a new value.
468                     if (!sizeOnly && prevCount != 0) {
469                         // Save away previous value.
470                         hist[prevIndex] = ((long)prevCount << 32)
471                                         | (LOW32 & prevValue);
472                     }
473                     prevValue = thisValue;
474                     prevCount = 1;
475                     prevIndex += 1;
476                 }
477             }
478             if (sizeOnly) {
479                 // Finished the sizing pass.  Allocate the histogram.
480                 hist = new long[prevIndex];
481             } else {
482                 break;  // done
483             }
484         }
485         return hist;
486     }
487 
488     /** Regroup the histogram, so that it becomes an approximate histogram
489      *  whose rows are of the given lengths.
490      *  If matrix rows must be split, the latter parts (larger values)
491      *  are placed earlier in the new matrix.
492      *  If matrix rows are joined, they are resorted into ascending order.
493      *  In the new histogram, the counts are averaged over row entries.
494      */
495     private static
496     int[][] regroupHistogram(int[][] matrix, int[] groups) {
497         long oldEntries = 0;
498         for (int i = 0; i < matrix.length; i++) {
499             oldEntries += matrix[i].length-1;
500         }
501         long newEntries = 0;
502         for (int ni = 0; ni < groups.length; ni++) {
503             newEntries += groups[ni];
504         }
505         if (newEntries > oldEntries) {
506             int newlen = groups.length;
507             long ok = oldEntries;
508             for (int ni = 0; ni < groups.length; ni++) {
509                 if (ok < groups[ni]) {
510                     int[] newGroups = new int[ni+1];
511                     System.arraycopy(groups, 0, newGroups, 0, ni+1);
512                     groups = newGroups;
513                     groups[ni] = (int) ok;
514                     ok = 0;
515                     break;
516                 }
517                 ok -= groups[ni];
518             }
519         } else {
520             long excess = oldEntries - newEntries;
521             int[] newGroups = new int[groups.length+1];
522             System.arraycopy(groups, 0, newGroups, 0, groups.length);
523             newGroups[groups.length] = (int) excess;
524             groups = newGroups;
525         }
526         int[][] newMatrix = new int[groups.length][];
527         // Fill pointers.
528         int i = 0;  // into matrix
529         int jMin = 1;
530         int jMax = matrix[i].length;
531         for (int ni = 0; ni < groups.length; ni++) {
532             int groupLength = groups[ni];
533             int[] group = new int[1+groupLength];
534             long groupWeight = 0;  // count of all in new group
535             newMatrix[ni] = group;
536             int njFill = 1;
537             while (njFill < group.length) {
538                 int len = group.length - njFill;
539                 while (jMin == jMax) {
540                     jMin = 1;
541                     jMax = matrix[++i].length;
542                 }
543                 if (len > jMax - jMin)  len = jMax - jMin;
544                 groupWeight += (long) matrix[i][0] * len;
545                 System.arraycopy(matrix[i], jMax - len, group, njFill, len);
546                 jMax -= len;
547                 njFill += len;
548             }
549             Arrays.sort(group, 1, group.length);
550             // compute average count of new group:
551             group[0] = (int) ((groupWeight + groupLength/2) / groupLength);
552         }
553         assert(jMin == jMax);
554         assert(i == matrix.length-1);
555         return newMatrix;
556     }
557 
558     public static
559     Histogram makeByteHistogram(InputStream bytes) throws IOException {
560         byte[] buf = new byte[1<<12];
561         int[] tally = new int[1<<8];
562         for (int nr; (nr = bytes.read(buf)) > 0; ) {
563             for (int i = 0; i < nr; i++) {
564                 tally[buf[i] & 0xFF] += 1;
565             }
566         }
567         // Build a matrix.
568         int[][] matrix = new int[1<<8][2];
569         for (int i = 0; i < tally.length; i++) {
570             matrix[i][0] = tally[i];
571             matrix[i][1] = i;
572         }
573         return new Histogram(matrix);
574     }
575 
576     /** Slice and sort the given input array. */
577     private static
578     int[] sortedSlice(int[] valueSequence, int start, int end) {
579         if (start == 0 && end == valueSequence.length &&
580             isSorted(valueSequence, 0, false)) {
581             return valueSequence;
582         } else {
583             int[] slice = new int[end-start];
584             System.arraycopy(valueSequence, start, slice, 0, slice.length);
585             Arrays.sort(slice);
586             return slice;
587         }
588     }
589 
590     /** Tell if an array is sorted. */
591     private static
592     boolean isSorted(int[] values, int from, boolean strict) {
593         for (int i = from+1; i < values.length; i++) {
594             if (strict ? !(values[i-1] < values[i])
595                        : !(values[i-1] <= values[i])) {
596                 return false;  // found witness to disorder
597             }
598         }
599         return true;  // no witness => sorted
600     }
601 
602     /** Clone and sort the array, if not already sorted. */
603     private static
604     int[] maybeSort(int[] values) {
605         if (!isSorted(values, 0, false)) {
606             values = values.clone();
607             Arrays.sort(values);
608         }
609         return values;
610     }
611 
612 
613     /// Debug stuff follows.
614 
615     private boolean assertWellFormed(int[] valueSequence) {
616 /*
617         // Sanity check.
618         int weight = 0;
619         int vlength = 0;
620         for (int i = 0; i < matrix.length; i++) {
621             int vlengthi = (matrix[i].length-1);
622             int count = matrix[i][0];
623             assert(vlengthi > 0);  // no empty rows
624             assert(count > 0);  // no impossible rows
625             vlength += vlengthi;
626             weight += count * vlengthi;
627         }
628         assert(isSorted(values, 0, true));
629         // make sure the counts all add up
630         assert(totalWeight == weight);
631         assert(vlength == values.length);
632         assert(vlength == counts.length);
633         int weight2 = 0;
634         for (int i = 0; i < counts.length; i++) {
635             weight2 += counts[i];
636         }
637         assert(weight2 == weight);
638         int[] revcol1 = new int[matrix.length];  //1st matrix colunm
639         for (int i = 0; i < matrix.length; i++) {
640             // spot checking:  try a random query on each matrix row
641             assert(matrix[i].length > 1);
642             revcol1[matrix.length-i-1] = matrix[i][0];
643             assert(isSorted(matrix[i], 1, true));
644             int rand = (matrix[i].length+1) / 2;
645             int val = matrix[i][rand];
646             int count = matrix[i][0];
647             int pos = Arrays.binarySearch(values, val);
648             assert(values[pos] == val);
649             assert(counts[pos] == matrix[i][0]);
650             if (valueSequence != null) {
651                 int count2 = 0;
652                 for (int j = 0; j < valueSequence.length; j++) {
653                     if (valueSequence[j] == val)  count2++;
654                 }
655                 assert(count2 == count);
656             }
657         }
658         assert(isSorted(revcol1, 0, true));
659 //*/
660         return true;
661     }
662 
663 /*
664     public static
665     int[] readValuesFrom(InputStream instr) {
666         return readValuesFrom(new InputStreamReader(instr));
667     }
668     public static
669     int[] readValuesFrom(Reader inrdr) {
670         inrdr = new BufferedReader(inrdr);
671         final StreamTokenizer in = new StreamTokenizer(inrdr);
672         final int TT_NOTHING = -99;
673         in.commentChar('#');
674         return readValuesFrom(new Iterator() {
675             int token = TT_NOTHING;
676             private int getToken() {
677                 if (token == TT_NOTHING) {
678                     try {
679                         token = in.nextToken();
680                         assert(token != TT_NOTHING);
681                     } catch (IOException ee) {
682                         throw new RuntimeException(ee);
683                     }
684                 }
685                 return token;
686             }
687             public boolean hasNext() {
688                 return getToken() != StreamTokenizer.TT_EOF;
689             }
690             public Object next() {
691                 int ntok = getToken();
692                 token = TT_NOTHING;
693                 switch (ntok) {
694                 case StreamTokenizer.TT_EOF:
695                     throw new NoSuchElementException();
696                 case StreamTokenizer.TT_NUMBER:
697                     return new Integer((int) in.nval);
698                 default:
699                     assert(false);
700                     return null;
701                 }
702             }
703             public void remove() {
704                 throw new UnsupportedOperationException();
705             }
706         });
707     }
708     public static
709     int[] readValuesFrom(Iterator iter) {
710         return readValuesFrom(iter, 0);
711     }
712     public static
713     int[] readValuesFrom(Iterator iter, int initSize) {
714         int[] na = new int[Math.max(10, initSize)];
715         int np = 0;
716         while (iter.hasNext()) {
717             Integer val = (Integer) iter.next();
718             if (np == na.length) {
719                 int[] na2 = new int[np*2];
720                 System.arraycopy(na, 0, na2, 0, np);
721                 na = na2;
722             }
723             na[np++] = val.intValue();
724         }
725         if (np != na.length) {
726             int[] na2 = new int[np];
727             System.arraycopy(na, 0, na2, 0, np);
728             na = na2;
729         }
730         return na;
731     }
732 
733     public static
734     Histogram makeByteHistogram(byte[] bytes) {
735         try {
736             return makeByteHistogram(new ByteArrayInputStream(bytes));
737         } catch (IOException ee) {
738             throw new RuntimeException(ee);
739         }
740     }
741 
742     public static
743     void main(String[] av) throws IOException {
744         if (av.length > 0 && av[0].equals("-r")) {
745             int[] values = new int[Integer.parseInt(av[1])];
746             int limit = values.length;
747             if (av.length >= 3) {
748                 limit  = (int)( limit * Double.parseDouble(av[2]) );
749             }
750             Random rnd = new Random();
751             for (int i = 0; i < values.length; i++) {
752                 values[i] = rnd.nextInt(limit);;
753             }
754             Histogram rh = new Histogram(values);
755             rh.print("random", System.out);
756             return;
757         }
758         if (av.length > 0 && av[0].equals("-s")) {
759             int[] values = readValuesFrom(System.in);
760             Random rnd = new Random();
761             for (int i = values.length; --i > 0; ) {
762                 int j = rnd.nextInt(i+1);
763                 if (j < i) {
764                     int tem = values[i];
765                     values[i] = values[j];
766                     values[j] = tem;
767                 }
768             }
769             for (int i = 0; i < values.length; i++)
770                 System.out.println(values[i]);
771             return;
772         }
773         if (av.length > 0 && av[0].equals("-e")) {
774             // edge cases
775             new Histogram(new int[][] {
776                 {1, 11, 111},
777                 {0, 123, 456},
778                 {1, 111, 1111},
779                 {0, 456, 123},
780                 {3},
781                 {},
782                 {3},
783                 {2, 22},
784                 {4}
785             }).print(System.out);
786             return;
787         }
788         if (av.length > 0 && av[0].equals("-b")) {
789             // edge cases
790             Histogram bh = makeByteHistogram(System.in);
791             bh.print("bytes", System.out);
792             return;
793         }
794         boolean regroup = false;
795         if (av.length > 0 && av[0].equals("-g")) {
796             regroup = true;
797         }
798 
799         int[] values = readValuesFrom(System.in);
800         Histogram h = new Histogram(values);
801         if (!regroup)
802             h.print(System.out);
803         if (regroup) {
804             int[] groups = new int[12];
805             for (int i = 0; i < groups.length; i++) {
806                 groups[i] = 1<<i;
807             }
808             int[][] gm = regroupHistogram(h.getMatrix(), groups);
809             Histogram g = new Histogram(gm);
810             System.out.println("h.getBitLength(g) = "+
811                                h.getBitLength(g.getBitMetric()));
812             System.out.println("g.getBitLength(h) = "+
813                                g.getBitLength(h.getBitMetric()));
814             g.print("regrouped", System.out);
815         }
816     }
817 //*/
818 }